//                 _              
//                | |             
//   __ _ _ __ ___| |__ __  _____ 
//  / _` | '__/ __| '_ \\ \/ / __|
// | (_| | | | (__| | | |>  <\__ \
//  \__,_|_|  \___|_| |_/_/\_\___/
//                                
// Copyright 2010 Scott Dixon http://www.archxs.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package com.archxs.commons.collections;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.junit.Assert;
import org.junit.Test;

import com.archxs.commons.Variant;

/**
 * Tests to cover implementations of the {@link VariantProperties} interface.
 */
public abstract class VariantPropertiesTestCommon {

    /**
     * Key used for the most basic unit tests.
     */
    public final static String SIMPLE_KEY = "foo";
    
    /**
     * Value used for the most basic unit tests;
     */
    public final static String[] SIMPLE_VALUE = new String[]{"bar"};
    
    // +-----------------------------------------------------------------------+
    // | UNIT TESTS
    // +-----------------------------------------------------------------------+
    @Test
    public void testCreateProperties() {
        Assert.assertNotNull(createTestSubject(new HashMap<String,String[]>()));
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testNullValues() {
        createTestSubject(null);
    }
    
    @Test
    public void testSize() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        Assert.assertEquals(1, testSubject.size());
    }

    @Test
    public void testIsEmpty() {
        final VariantProperties testSubject = createTestSubject(new HashMap<String,String[]>());
        Assert.assertTrue(testSubject.isEmpty());
        testSubject.put(SIMPLE_KEY, Variant.valueOf(SIMPLE_VALUE));
        Assert.assertFalse(testSubject.isEmpty());
    }
    
    @Test
    public void testPut() {
        final Map<String,String[]> testValues = new HashMap<String,String[]>();
        final VariantProperties testSubject = createTestSubject(testValues);
        Assert.assertEquals(0, testSubject.size());
        Assert.assertEquals(Variant.valueOf((String[])null), testSubject.put("foo", Variant.valueOf("bar")));
        Assert.assertEquals(1, testSubject.size());
        final Variant fooValue = testSubject.get("foo");
        Assert.assertNotNull(fooValue);
        Assert.assertEquals(Variant.valueOf("bar"), fooValue);
        if (isLive()) {
            Assert.assertEquals(1, testValues.size());
            final String[] value = testValues.get("foo");
            Assert.assertNotNull(value);
            Assert.assertEquals(1, value.length);
            Assert.assertEquals("bar", value[0]);
        }
        Assert.assertEquals(fooValue, testSubject.put("foo", Variant.valueOf("bah")));
        Assert.assertEquals(1, testSubject.size());
        final Variant bahValue = testSubject.get("foo");
        Assert.assertNotNull(bahValue);
        Assert.assertEquals(Variant.valueOf("bah"), bahValue);
        if (isLive()) {
            Assert.assertEquals(1, testValues.size());
            final String[] value = testValues.get("foo");
            Assert.assertNotNull(value);
            Assert.assertEquals(1, value.length);
            Assert.assertEquals("bah", value[0]);
        }
    }
    
    @Test
    public void testClear() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        Assert.assertEquals(1, testSubject.size());
        testSubject.clear();
        Assert.assertEquals(0, testSubject.size());
        if (isLive()) {
            Assert.assertEquals(0, testValues.size());
            testValues.put(SIMPLE_KEY, SIMPLE_VALUE);
            Assert.assertEquals(1, testValues.size());
        }
    }
    
    @Test
    public void testRemove() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        Assert.assertEquals(1, testSubject.size());
        Assert.assertTrue(testSubject.containsKey(SIMPLE_KEY));
        Assert.assertEquals(Variant.valueOf(SIMPLE_VALUE), testSubject.remove(SIMPLE_KEY));
        Assert.assertEquals(0, testSubject.size());
        Assert.assertFalse(testSubject.containsKey(SIMPLE_KEY));
        if (isLive()) {
            Assert.assertEquals(0, testValues.size());
        }
    }
    
    @Test
    public void testContainsValue() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Variant simpleValue = testSubject.get(SIMPLE_KEY);
        
        Assert.assertFalse(testSubject.containsValue(null));
        Assert.assertFalse(testSubject.containsValue("not a value"));
        Assert.assertTrue(testSubject.containsValue(simpleValue));
    }
    
    @Test
    public void testPutAll() {
        final Map<String,Variant> testValues = new HashMap<String,Variant>();
        for(int i = 0; i < 100; ++i) {
            testValues.put(String.format("key_%d", i), Variant.valueOf(String.format("value_%d", i)));
        }
        final int testValuesSize = testValues.size();
        final VariantProperties testSubject = createTestSubject(new HashMap<String,String[]>());
        Assert.assertEquals(0, testSubject.size());
        testSubject.putAll(testValues);
        Assert.assertEquals(testValuesSize, testSubject.size());
        for(final Variant value : testValues.values()) {
            Assert.assertTrue(testSubject.containsValue(value));
        }
        testValues.clear();
        Assert.assertEquals("putAll must copy values into the collection. It " +
                "looks like the put map was being referred to by the test subject " +
                "collection.", testValuesSize, testSubject.size());
    }
    
    @Test
    public void testKeySet() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        Assert.assertEquals(testValues.keySet(), testSubject.keySet());
        if (isLive()) {
            testValues.clear();
            Assert.assertEquals(0, testSubject.keySet().size());
        }
    }
    
    @Test
    public void testValues() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        Assert.assertEquals(1, testSubject.values().size());
    }
    
    // +-----------------------------------------------------------------------+
    // | UNIT TEST :: ENTRY SET
    // +-----------------------------------------------------------------------+
    @Test
    public void testEntrySetCreateEmpty() {
        final Map<String,String[]> testValues = new HashMap<String,String[]>();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertNotNull(entrySet);
        Assert.assertEquals(0, entrySet.size());
    }
    
    @Test
    public void testEntrySetIterator() {
        // This test requires the map ordering to be stable. You must retain the
        // use of LinkedHashMap for this test to succeed. This is not an inherent
        // property of the class under test however.
        final Map<String,String[]> testValues = new LinkedHashMap<String,String[]>();
        final int maxX = 100;
        final int maxY = 4;
        for(int x = 0; x < maxX; ++x) {
            final String[] value = new String[maxY];
            for(int y = 0; y < maxY; ++y) {
                value[y] = Integer.toString(y);
            }
            testValues.put(Integer.toString(x), value);
        }
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        final Iterator<Entry<String,Variant> > iter = entrySet.iterator();
        int iterations = 0;
        while(iter.hasNext()) {
            final Entry<String,Variant> entry = iter.next();
            Assert.assertNotNull(entry);
            Assert.assertEquals(Integer.toString(iterations), entry.getKey());
            Assert.assertTrue(Arrays.equals(testValues.get(entry.getKey()), entry.getValue().getArray(new String[0])));
            ++iterations;
        }
        Assert.assertEquals(maxX, iterations);
    }
    
    @Test
    public void testEntrySetIteratorRemove() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        final int orgSize = entrySet.size();
        final Iterator<Entry<String,Variant> > iter = entrySet.iterator();
        Assert.assertTrue(iter.hasNext());
        final Entry<String,Variant> next = iter.next();
        Assert.assertNotNull(next);
        Assert.assertEquals(SIMPLE_KEY, next.getKey());
        iter.remove();
        Assert.assertEquals(orgSize - 1, entrySet.size());
        if (isLive()) {
            Assert.assertEquals(orgSize - 1, testValues.size());
        }
    }
    
    @Test
    public void testEntrySetContains() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertTrue(entrySet.contains(entrySet.iterator().next()));
        Assert.assertFalse(entrySet.contains(null));
        Assert.assertFalse(entrySet.contains(new Entry<String,Variant>() {

            @Override
            public String getKey() {
                return "not in the map";
            }

            @Override
            public Variant getValue() {
                return null;
            }

            @Override
            public Variant setValue(final Variant value) {
                return null;
            }
        }));
        Assert.assertFalse(entrySet.contains(new Entry<String,Variant>() {

            @Override
            public String getKey() {
                return SIMPLE_KEY;
            }

            @Override
            public Variant getValue() {
                return Variant.valueOf("Not a value in the map");
            }

            @Override
            public Variant setValue(final Variant value) {
                return null;
            }
        }));
    }
    
    @Test
    public void testEntrySetContainsAll() {
        final Map<String,String[]> testStorage = new HashMap<String,String[]>();
        final VariantProperties testSubject = createTestSubject(testStorage);
        final Set<Entry<String,Variant> > testValues = new HashSet<Entry<String,Variant> >();
        final int maxX = 10;
        final int maxY = 1;
        for(int x = 0; x < maxX; ++x) {
            final String key = Integer.toString(x);
            final String[] valueArray = new String[maxY];
            for(int y = 0; y < maxY; ++y) {
                valueArray[y] = Integer.toString(y);
            }
            final Variant value = Variant.valueOf(valueArray);
            testValues.add(new Entry<String,Variant>() {

                @Override
                public String getKey() {
                    return key;
                }

                @Override
                public Variant getValue() {
                    return value;
                }

                @Override
                public Variant setValue(final Variant value) {
                    Assert.fail("setValue was called unexpectedly.");
                    return null;
                }
                
            });
        }
        
        // Copy into the test subject so that the two can be changed independently.
        for(final Entry<String,Variant> value : testValues) {
            testSubject.put(value.getKey(), value.getValue());
        }
        Assert.assertEquals(maxX, testSubject.size());
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertTrue(entrySet.containsAll(testValues));
        Assert.assertTrue(testValues.remove(testValues.iterator().next()));
        Assert.assertTrue(entrySet.containsAll(testValues));
        entrySet.clear();
        Assert.assertFalse(entrySet.containsAll(testValues));
        testValues.clear();
        Assert.assertFalse(entrySet.containsAll(testValues));
    }

    @Test
    public void testEntrySetContainsAllNull() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertFalse(entrySet.containsAll(null));
    }
    
    @Test
    public void testEntrySetIsEmpty() {
        final Map<String,String[]> testValues = new HashMap<String,String[]>();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertTrue(entrySet.isEmpty());
    }
    
    @Test
    public void testEntrySetCreate() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertNotNull(entrySet);
        Assert.assertEquals(testValues.size(), entrySet.size());
        boolean foundSimpleKey = false;
        for(final Entry<String,Variant> entry : entrySet) {
            Assert.assertNotNull(entry);
            if (entry.getKey().equals(SIMPLE_KEY)) {
                foundSimpleKey = true;
                final Variant value = entry.getValue();
                Assert.assertNotNull(value);
                final String[] stringArray = value.getArray(new String[0]);
                Assert.assertTrue(Arrays.equals(SIMPLE_VALUE,stringArray));
                final Variant newValue = Variant.valueOf("testEntrySetCreate");
                Assert.assertEquals(value, entry.setValue(newValue));
                Assert.assertEquals(newValue, entry.getValue());
                if (isLive()) {
                    Assert.assertTrue(Arrays.equals(newValue.getArray(new String[0]), testValues.get(SIMPLE_KEY)));
                }
            }
        }
        Assert.assertTrue(foundSimpleKey);
    }
    
    @Test
    public void testAddToEntrySet() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertNotNull(entrySet);
        Assert.assertEquals(testValues.size(), entrySet.size());
        final String newKey = "testAddToEntrySet";
        final Variant newValue = Variant.valueOf("testAddToEntrySet_value");
        final Entry<String,Variant> newEntry = new Entry<String,Variant>() {

            @Override
            public String getKey() {
                return newKey;
            }

            @Override
            public Variant getValue() {
                return newValue;
            }

            @Override
            public Variant setValue(final Variant value) {
                Assert.fail("setValue on entry being added was called unexpectedly.");
                return null;
            }
            
        };
        final int oldSize = entrySet.size();
        Assert.assertFalse(entrySet.add(newEntry));
        Assert.assertTrue(entrySet.add(newEntry));
        Assert.assertEquals(oldSize + 1, entrySet.size());
        Assert.assertTrue(entrySet.contains(newEntry));
        if (isLive()) {
            Assert.assertTrue(Arrays.equals(newValue.getArray(new String[0]), testValues.get(newKey)));
        }
    }
    
    @Test
    public void testEntrySetToArray() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        @SuppressWarnings("unchecked")
        final Entry<String,Variant>[] array = (Entry<String,Variant>[])entrySet.toArray();
        Assert.assertNotNull(array);
        Assert.assertEquals(testValues.size(), array.length);
    }
    
    @Test(expected=NullPointerException.class)
    public void testEntrySetToArrayNPE() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        entrySet.toArray(null);
    }
    
    @Test
    public void testEntrySetToArrayWithStorage() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        @SuppressWarnings("unchecked")
        final Entry<String,Variant>[] inoutStorage = (Entry<String, Variant>[]) new Entry<?,?>[testValues.size()];
        Assert.assertSame(inoutStorage, entrySet.toArray(inoutStorage));
        for(int i = 0; i < inoutStorage.length; ++i) {
            Assert.assertNotNull(inoutStorage[i]);
        }
    }
    
    @Test
    public void testEntrySetClear() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertTrue(entrySet.size() > 0);
        entrySet.clear();
        Assert.assertEquals(0, entrySet.size());
        if (isLive()) {
            Assert.assertEquals(0, testValues.size());
        }
    }
    
    @Test
    public void testEntrySetRemove() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        final Entry<String,Variant> entry = entrySet.iterator().next();
        Assert.assertTrue(entrySet.remove(entry));
        Assert.assertFalse(entrySet.remove(entry));
        if (isLive()) {
            Assert.assertFalse(testValues.containsKey(entry.getKey()));
        }
    }
    
    @Test
    public void testEntrySetNotRemoved() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        final int size = entrySet.size();
        Assert.assertTrue(size > 0);
        Assert.assertFalse(entrySet.remove(new Object()));
        Assert.assertEquals(size, entrySet.size());
        Assert.assertFalse(entrySet.remove(new Entry<String,Variant>() {

            @Override
            public String getKey() {
                return "testEntrySetNotRemoved";
            }

            @Override
            public Variant getValue() {
                return Variant.valueOf("testEntrySetNotRemoved_value");
            }

            @Override
            public Variant setValue(final Variant value) {
                Assert.fail("setValue method was called unexpectedly.");
                return null;
            }
        }));
        Assert.assertEquals(size, entrySet.size());
    }
    
    @Test
    public void testEntrySetAddAll() {
        final Map<String,String[]> testValues = new HashMap<String,String[]>();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        final int maxX = 10;
        final int oldSize = entrySet.size();
        final List<Entry<String,Variant> > addedValues = new ArrayList<Entry<String,Variant> >(maxX);
        for(int x = 0; x < maxX; ++x) {
            final String key = Integer.toString(x);
            final Variant value = Variant.valueOf(String.format("value_%d", x));
            addedValues.add(new Entry<String,Variant>() {

                @Override
                public String getKey() {
                    return key;
                }

                @Override
                public Variant getValue() {
                    return value;
                }

                @Override
                public Variant setValue(final Variant value) {
                    Assert.fail("setValue called unexpectedly");
                    return null;
                }
                
            });
        }
        Assert.assertTrue(entrySet.addAll(addedValues));
        Assert.assertEquals(oldSize + maxX, entrySet.size());
        if (isLive()) {
            Assert.assertEquals(testValues.size(), entrySet.size());
        }
    }
    
    @Test
    public void testEntrySetAddNullToAll() {
        final Map<String,String[]> testValues = new HashMap<String,String[]>();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertFalse(entrySet.addAll(null));
    }
    
    @SuppressWarnings("unchecked")
    @Test
    public void testEntrySetAddSameToAll() {
        final Map<String,String[]> testValues = new HashMap<String,String[]>();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        final String key = Integer.toString(0);
        final Variant value = Variant.valueOf(String.format("value_%d", 0));
        final Entry<String,Variant> oneEntry = new Entry<String,Variant>() {

            @Override
            public String getKey() {
                return key;
            }

            @Override
            public Variant getValue() {
                return value;
            }

            @Override
            public Variant setValue(final Variant value) {
                Assert.fail("setValue called unexpectedly");
                return null;
            }
            
        };
        entrySet.add(oneEntry);
        Assert.assertTrue(entrySet.contains(oneEntry));
        Assert.assertFalse(entrySet.addAll(Arrays.asList(oneEntry)));
    }
    
    @Test
    public void testEntrySetRetainAll() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final int orgSize = testValues.size();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Entry<String,Variant> addedEntry = new Entry<String,Variant>() {

            @Override
            public String getKey() {
                return "1";
            }

            @Override
            public Variant getValue() {
                return Variant.valueOf("1");
            }

            @Override
            public Variant setValue(final Variant value) {
                Assert.fail("Unexpected call to setValue.");
                return null;
            }
            
        };
        testSubject.put(addedEntry.getKey(), addedEntry.getValue());
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertEquals(orgSize + 1, entrySet.size());
        @SuppressWarnings("unchecked")
        final Collection<Entry<String,Variant> > retain = Arrays.asList(addedEntry);
        Assert.assertTrue(entrySet.retainAll(retain));
        Assert.assertEquals(1, entrySet.size());
    }
    
    @Test
    public void testEntrySetRetainAllNull() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final int orgSize = testValues.size();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertFalse(entrySet.retainAll(null));
        Assert.assertEquals(orgSize, entrySet.size());
    }
    
    @Test
    public void testEntrySetRemoveAll() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        final Collection<Entry<String,Variant> > removeList = new ArrayList<Entry<String,Variant> >();
        removeList.addAll(entrySet);
        Assert.assertTrue(removeList.size() > 0);
        Assert.assertTrue(entrySet.removeAll(removeList));
        Assert.assertEquals(0, entrySet.size());
    }
    
    @Test
    public void testEntrySetRemoveAllNull() {
        final Map<String,String[]> testValues = createNewSimpleTestData();
        final int orgSize = testValues.size();
        final VariantProperties testSubject = createTestSubject(testValues);
        final Set<Entry<String,Variant> > entrySet = testSubject.entrySet();
        Assert.assertFalse(entrySet.removeAll(null));
        Assert.assertEquals(orgSize, entrySet.size());
    }
    // +-----------------------------------------------------------------------+
    // | TEMPLATE METHODS
    // +-----------------------------------------------------------------------+
    /**
     * Requests that the implementing class create a new VariantProperties instance
     * populated with the provided values.
     * @param  testValues   Values to populate the test subject with.
     * @return A new VariantProperties instance populated with the test values.
     */
    protected abstract VariantProperties createTestSubject(Map<String,String[]> testValues);
    
    /**
     * Queries the implementing class about the "liveness" of its collection objects
     * returned from the {@link VariantPropertiesTestCommon#createTestSubject(Map)}
     * method. 
     * @return <code>true</code> if the VariantProperties classes created for testing
     *         are using the test values provided "live" (i.e. changes to the VariantProperties
     *         collection will cause changes to the test values). <code>false</code>
     *         indicates that all test values are, at-least, shallow copied into
     *         the test collection created.
     */
    protected abstract boolean isLive();
    
    // +-----------------------------------------------------------------------+
    // | PRIVATE
    // +-----------------------------------------------------------------------+
    private Map<String, String[]> createNewSimpleTestData() {
        final Map<String,String[]> testValues = new HashMap<String,String[]>();
        testValues.put(SIMPLE_KEY, Arrays.copyOf(SIMPLE_VALUE, SIMPLE_VALUE.length));
        return testValues;
    }
}
